package com.phychips.rcp;

/**
 * RcpPacket defines a packet and the functions to encode/decode/check the
 * packet.
 * 
 * 
 */
public class RcpPacket
{
    /** packet. byte array */
    protected byte[] packet;

    /** packet length */
    protected int packet_len;
    /** payload length */
    protected int payload_len;
    /** maximum payload length */
    private int max_payload_len;
    public static final int PAYLOAD_INDEX = 5;    
    private static final int PACKET_LEN_EXCLUDING_PAYLOAD = 8;

    public static final RcpPacket INSTANCE = new RcpPacket();

    private RcpPacket()
    {
	this.max_payload_len = 256;
	this.packet = new byte[max_payload_len + PACKET_LEN_EXCLUDING_PAYLOAD];

	return;
    }
    
    public static RcpPacket getInstance()
    {
	return INSTANCE;
    }
  
    private byte getPreamble()
    {
	return packet[0];
    }

    private byte getType()
    {
	return packet[1];
    }

    private byte getCode()
    {
	return packet[2];
    }

    private byte getEndMask()
    {
	return packet[PAYLOAD_INDEX + payload_len];
    }

    private int getUCRC16() throws RcpException
    {
	return RcpLib.convertByteArrayToInt(packet, PAYLOAD_INDEX + payload_len
		+ 1, 2);
    }

    /**
     * This checks the required packet size. It exceeds the current maximum, the
     * new packet is allocated.
     * 
     * @param payload_len
     */
    public void checkPacketSize(int payload_len, boolean crc)
    {
	if (payload_len > this.max_payload_len)
	{
	    this.max_payload_len = payload_len;
	    this.packet = new byte[max_payload_len
		    + PACKET_LEN_EXCLUDING_PAYLOAD];
	}
	this.payload_len = payload_len;
	
	if(crc)
	{
	    this.packet_len = PAYLOAD_INDEX + payload_len + 3;
	}
	else
	{
	    this.packet_len = PAYLOAD_INDEX + payload_len + 1;
	}

	return;
    }

    /**
     * This starts encoding packet.
     * 
     * @param type
     * @param code
     * @param payload_len
     * @throws RcpException
     */
    public void startEncodePacket(byte type, byte code, int payload_len, boolean crc)
	    throws RcpException
    {
	checkPacketSize(payload_len, crc);

	packet[0] = RcpConst.PREAMBLE;
	packet[1] = type;
	packet[2] = code;
	RcpLib.convertIntToByteArray(payload_len, packet, 3, 2);
	packet[PAYLOAD_INDEX + payload_len] = RcpConst.ENDMARK;
    }

    /**
     * This adds the CRC value to the packet.
     * 
     * @throws RcpException
     */
    public void endEncodePacket() throws RcpException
    {
	RcpLib.convertIntToByteArray(RcpLib.calculateCRC(this), packet,
		PAYLOAD_INDEX + payload_len + 1, 2);
    }

    private void checkValidPayloadFormat0(int payload_len) throws RcpException
    {
	RcpException exception;

	if (this.payload_len != payload_len)
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    private void checkValidPayloadFormat1() throws RcpException
    {
	RcpException exception;

	if (this.payload_len != 0x01 || this.packet[PAYLOAD_INDEX] != 0x00)
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    private void checkValidPayloadFormat2() throws RcpException
    {
	RcpException exception;

	if ((RcpLib.covertUShorttoInt(this.packet[PAYLOAD_INDEX + 6]) + 7 != this.payload_len)
		|| ((this.packet[PAYLOAD_INDEX + 6] & 0x7F) != 0))
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    private void checkValidPayloadFormat3() throws RcpException
    {
	RcpException exception;

	if (this.payload_len != 0x02
		|| ((this.packet[PAYLOAD_INDEX + 1] & 0x07) != 0))
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    private void checkValidPayloadFormat4() throws RcpException
    {
	RcpException exception;

	if (this.payload_len != (RcpLib
		.covertUbytetoInt(this.packet[PAYLOAD_INDEX]) + 1))
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    private void checkValidPayloadFormat5() throws RcpException
    {
	RcpException exception;

	if (this.payload_len != 0x01 || this.packet[PAYLOAD_INDEX] != 0x1F)
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}
	return;
    }

    /**
     * This checks the validness of the packet format.
     * 
     * @throws RcpException
     */
    public void checkValidPacket() throws RcpException
    {
	RcpException exception;

	// general valid check
	if (getPreamble() != RcpConst.PREAMBLE
		|| (getType() != RcpConst.RCP_MSG_CMD
			&& getType() != RcpConst.RCP_MSG_RSP && getType() != RcpConst.RCP_MSG_NOTI)
		|| getEndMask() != RcpConst.ENDMARK)
	{
	    exception = new RcpException("invalid packet format",
		    RcpConst.INVALID_PACKET);
	    throw exception;
	}

	if (getUCRC16() != RcpLib.calculateCRC(this))
	{
	    exception = new RcpException("invalid packet: CRC mismatch",
		    RcpConst.INVALID_PACKET_CRC);
	    throw exception;
	}

	if (getType() == RcpConst.RCP_MSG_CMD)
	{
	    return;
	}

	// from here, check the validness of the response or the notification
	// packet
	try
	{
	    if (getCode() == RcpConst.RCP_CMD_CTL_RD_PWR)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_RD_INFO)
	    {
		; // no check
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_REGION)
	    {
		checkValidPayloadFormat0(0x01); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_REGION)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_CTL_RESET)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_C_SEL_PARAM)
	    {
		checkValidPayloadFormat2(); // format2
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_C_SEL_PARAM)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_C_QRY_PARAM)
	    {
		checkValidPayloadFormat3(); // format3
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_C_QRY_PARAM)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_CH)
	    {
		checkValidPayloadFormat0(0x02); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_CH)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_FH_LBT)
	    {
		checkValidPayloadFormat0(0x0B); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_FH_LBT)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_TX_PWR)
	    {
		checkValidPayloadFormat0(0x02); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_TX_PWR)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_CTL_CW)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_READ_C_UII)
	    {
		// checkValidPayloadFormat0(0x0E); // no check
	    }
	    else if (getCode() == RcpConst.RCP_CMD_READ_C_USER_DT)
	    {
		// checkValidPayloadFormat0(0x0E); // no check
	    }
	    else if (getCode() == RcpConst.RCP_CMD_STRT_AUTO_READ)
	    {
		if (getType() == RcpConst.RCP_MSG_RSP)
		{
		    checkValidPayloadFormat1(); // format1
		}
		else if (getType() == RcpConst.RCP_MSG_NOTI)
		{
		    checkValidPayloadFormat5(); // format0
		}
	    }
	    else if (getCode() == RcpConst.RCP_CMD_STOP_AUTO_READ)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_READ_C_DT)
	    {
		; // no check
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_HOPPING_TBL)
	    {
		checkValidPayloadFormat4(); // format4
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_HOPPING_TBL)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_MODULATION)
	    {
		checkValidPayloadFormat0(0x04); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_MODULATION)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_ANTICOL_MODE)
	    {
		checkValidPayloadFormat0(0x01); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_SET_ANTICOL_MODE)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_STRT_AUTO_READ_EX)
	    {
		if (getType() == RcpConst.RCP_MSG_RSP)
		{
		    checkValidPayloadFormat1(); // format1
		}
		else if (getType() == RcpConst.RCP_MSG_NOTI)
		{
		    checkValidPayloadFormat5(); // format0
		}
	    }
	    else if (getCode() == RcpConst.RCP_CMD_READ_C_UII_TID)
	    {
		if (getType() == RcpConst.RCP_MSG_RSP)
		{
		    checkValidPayloadFormat1(); // format1
		}
		else if (getType() == RcpConst.RCP_MSG_NOTI)
		{
		    checkValidPayloadFormat5(); // format0
		}
	    }
	    else if (getCode() == RcpConst.RCP_CMD_STOP_AUTO_READ_EX)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_WRITE_C_DT)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_BLOCKWRITE_C_DT)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_BLOCKERASE_C_DT)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_KILL_RECOM_C)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_LOCK_C)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_TEMPERATURE)
	    {
		checkValidPayloadFormat0(0x01); // format0
	    }
	    else if (getCode() == RcpConst.RCP_CMD_GET_RSSI)
	    {
		checkValidPayloadFormat0(0x02); // format0
	    }
	    else if (getCode() == RcpConst.RCP_UPDATE_REGISTRY)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_ERASE_REGISTRY)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.RCP_GET_REGISTRY_ITEM)
	    {
		; // no check
	    }
	    else if (getCode() == RcpConst.GET_POWER_TABLE)
	    {
		; // no check
	    }
	    else if (getCode() == RcpConst.SET_POWER_TABLE)
	    {
		checkValidPayloadFormat1(); // format1
	    }
	    else if (getCode() == RcpConst.COMMAND_FAIL)
	    {
		checkValidPayloadFormat0(0x01); // format0
	    }
	}
	catch (RcpException exception2)
	{
	    throw exception2;
	}
	return;
    }

    private boolean isResponseFailure()
    {
	if (getCode() == RcpConst.COMMAND_FAIL
		&& getType() == RcpConst.RCP_MSG_RSP)
	{
	    return true;
	}
	return false;
    }

    private boolean isNotificationFailure()
    {
	if (getCode() == RcpConst.COMMAND_FAIL
		&& getType() == RcpConst.RCP_MSG_NOTI)
	{
	    return true;
	}
	return false;
    }

    /**
     * This checks whether the response is expected or not.
     * 
     * @param code
     * @throws RcpException
     */
    public void checkExpectedResponse(byte code) throws RcpException
    {
	RcpException exception;

	if (isResponseFailure() == true)
	{
	    switch (this.packet[PAYLOAD_INDEX])
	    {
	    case RcpConst.READ_MEM_FAILURE:
		exception = new RcpException("fail to read tag memory",
			RcpConst.READ_MEM_FAILURE);
		break;
	    case RcpConst.WRITE_DATA_FAILURE:
		exception = new RcpException("fail to write data",
			RcpConst.WRITE_DATA_FAILURE);
		break;
	    case RcpConst.BUSY_READ_MULTIPLE:
		exception = new RcpException(
			"read type C tag ID multiple in operation",
			RcpConst.BUSY_READ_MULTIPLE);
		break;
	    case RcpConst.NOT_IN_READ_MULTIPLE:
		exception = new RcpException(
			"not in mode - read type C tag ID multiple",
			RcpConst.NOT_IN_READ_MULTIPLE);
		break;
	    case RcpConst.INVALID_COMMAND_PARAM:
		exception = new RcpException("invalid command parameters",
			RcpConst.INVALID_COMMAND_PARAM);
		break;
	    case RcpConst.KILL_TAG_FAILURE:
		exception = new RcpException("fail to kill a tag",
			RcpConst.KILL_TAG_FAILURE);
		break;
	    case RcpConst.LOCK_TAG_FAILURE:
		exception = new RcpException("fail to lock a tag",
			RcpConst.LOCK_TAG_FAILURE);
		break;
	    case RcpConst.READ_TAG_FAILURE:
		exception = new RcpException("fail to read a tag",
			RcpConst.READ_TAG_FAILURE);
		break;
	    case RcpConst.NOT_SUPPORTED_COMMAND:
		exception = new RcpException("not supported command",
			RcpConst.NOT_SUPPORTED_COMMAND);
		break;
	    case RcpConst.CRC_ERROR:
		exception = new RcpException("CRC error", RcpConst.CRC_ERROR);
		break;
	    default:
		exception = new RcpException("unknown failure",
			RcpConst.UNKNOWN_FAILURE);
		break;
	    }

	    throw exception;
	}

	if (getType() != RcpConst.RCP_MSG_RSP)
	{
	    exception = new RcpException("not response", RcpConst.NOT_RESPONSE);
	    throw exception;
	}

	if (getCode() != code)
	{
	    exception = new RcpException("unexpected response",
		    RcpConst.UNEXPECTED_RESPONSE);
	    throw exception;
	}

	return;
    }

    /**
     * This checks whether the notification is expected or not.
     * 
     * @param code
     * @throws RcpException
     */
    public void checkExpectedNotification(byte code) throws RcpException
    {
	RcpException exception;

	if (isNotificationFailure() == true)
	{
	    switch (this.packet[PAYLOAD_INDEX])
	    {
	    case RcpConst.READ_MEM_FAILURE:
		exception = new RcpException("fail to read tag memory",
			RcpConst.READ_MEM_FAILURE);
		break;
	    case RcpConst.WRITE_DATA_FAILURE:
		exception = new RcpException("fail to write data",
			RcpConst.WRITE_DATA_FAILURE);
		break;
	    case RcpConst.BUSY_READ_MULTIPLE:
		exception = new RcpException(
			"read type C tag ID multiple in operation",
			RcpConst.BUSY_READ_MULTIPLE);
		break;
	    case RcpConst.NOT_IN_READ_MULTIPLE:
		exception = new RcpException(
			"not in mode - read type C tag ID multiple",
			RcpConst.NOT_IN_READ_MULTIPLE);
		break;
	    case RcpConst.INVALID_COMMAND_PARAM:
		exception = new RcpException("invalid command parameters",
			RcpConst.INVALID_COMMAND_PARAM);
		break;
	    case RcpConst.KILL_TAG_FAILURE:
		exception = new RcpException("fail to kill a tag",
			RcpConst.KILL_TAG_FAILURE);
		break;
	    case RcpConst.LOCK_TAG_FAILURE:
		exception = new RcpException("fail to lock a tag",
			RcpConst.LOCK_TAG_FAILURE);
		break;
	    case RcpConst.READ_TAG_FAILURE:
		exception = new RcpException("fail to read a tag",
			RcpConst.READ_TAG_FAILURE);
		break;
	    case RcpConst.NOT_SUPPORTED_COMMAND:
		exception = new RcpException("not supported command",
			RcpConst.NOT_SUPPORTED_COMMAND);
		break;
	    case RcpConst.CRC_ERROR:
		exception = new RcpException("CRC error", RcpConst.CRC_ERROR);
		break;
	    default:
		exception = new RcpException("unknown failure",
			RcpConst.UNKNOWN_FAILURE);
		break;
	    }

	    throw exception;
	}

	if (getType() != RcpConst.RCP_MSG_NOTI)
	{
	    exception = new RcpException("not notificaiton",
		    RcpConst.NOT_NOTIFICATION);
	    throw exception;
	}

	if (getCode() != RcpConst.RCP_CMD_READ_C_UII)
	{
	    if (getCode() == code)
	    {
		exception = new RcpException("multiple read complete",
			RcpConst.AUTOREAD_COMPLETE);
	    }
	    else
	    {
		exception = new RcpException("unexpected notification",
			RcpConst.UNEXPECTED_NOTIFICATION);
		throw exception;
	    }
	    throw exception;
	}

	return;
    }
}
